#include "BVHnode.h"


BVHnode::BVHnode(){
	theShapes = list<Shape*>();
	thisIsLeaf = false;
	theLeft = NULL;
	theRight = NULL;
	theParent = NULL;
	theBBox = BBox();
}

BVHnode::BVHnode(float _xmin, float _ymin, float _zmin, float _xmax, float _ymax, float _zmax){
	//create a BBox with these min and max values
	theBBox = BBox(_xmin, _ymin, _zmin, _xmax, _ymax, _zmax);
}

BVHnode::~BVHnode(){
}

bool BVHnode::build(list<Shape*> _li, int _groupSize){
	//if the number of shapes is <= the target group size
	if ((int)_li.size() <= _groupSize){
		//printf("we're down to our group size\n");
		//add these shapes to the list
		theShapes = list<Shape*>(_li);
		//set the node type to leaf
		thisIsLeaf = true;
		//create a bounding box from the shapes
		list<Shape*>::iterator it;
		for (it = theShapes.begin(); it != theShapes.end(); it++){
			Shape* _s = *it;
			theBBox.addBBox(_s->getMinBound(), _s->getMaxBound());
		}
		//return true
		return true;
	}
	//printf("Not yet down to our group size\n");
	//get the bounding box for this group
	list<Shape*>::iterator it;
	for (it = _li.begin(); it != _li.end(); it++){
		Shape* _s = *it;
		theBBox.addBBox(_s->getMinBound(), _s->getMaxBound());
	}

	//select an axis: the longest one
	//sort the shapes on this axis
	vec3 dim = theBBox.dimensions();
	if (dim.x >= dim.y && dim.x >= dim.z){
		//sort on x
		_li.sort(Shape::compare_xCenter);
	}
	else if (dim.y >= dim.x && dim.y >= dim.z){
		//sort on y
		_li.sort(Shape::compare_yCenter);
	}
	if (dim.z >= dim.y && dim.z >= dim.x){
		//sort on z
		_li.sort(Shape::compare_zCenter);
	}
	
	//set the split axis
	//create a left BVHnode with the first half of the shapes
	theLeft = new BVHnode();
	size_t const halfSize = _li.size() / 2;
	list<Shape*>::iterator halfway = _li.begin();
	advance(halfway, halfSize);
	list<Shape*> leftList(_li.begin(), halfway);
	
	//create a right BVHnode with the second half of the shapes
	theRight = new BVHnode();
	list<Shape*> rightList(halfway, _li.end());

	bool leftGood = theLeft->build(leftList, _groupSize);
	bool rightGood = theRight->build(rightList, _groupSize);

	//return leftResult && rightResult
	return leftGood && rightGood;
}

bool BVHnode::intersect(Ray *_theRay, float &_t0, float &_t1){
	Stats::instance()->boxTest();	//increment the box intersection test count
	return theBBox.intersect(_theRay, _t0, _t1);
}

void BVHnode::intersect(Intersection &_int){
	//if this is an interior node
	if (!thisIsLeaf){
		//perform the intersection test on the left and right BVHnodes
		float left_t0 = -1e30f, left_t1 = 1e30f, right_t0 = -1e30f, right_t1 = 1e30f;
		bool leftInt = theLeft->intersect(_int.ray(), left_t0, left_t1);
		bool rightInt = theRight->intersect(_int.ray(), right_t0, right_t1);
		//if both sides intersected, do the closer one first
		if (leftInt && rightInt && right_t0 < left_t0){
			bool temp = leftInt;
			leftInt = rightInt;
			rightInt = temp;

			BVHnode* temp_n = theLeft;
			theLeft = theRight;
			theRight = temp_n;

			float tempF0 = left_t0;
			left_t0 = right_t0;
			right_t0 = tempF0;

			float tempF1 = left_t1;
			left_t1 = right_t1;
			right_t1 = tempF1;
		}
		if (leftInt){
			//determine the closest intersection point in front of the ray
			float left_t = (left_t0 < 0) ? left_t1 : left_t0;
			bool isInside = false;
			if (left_t0 < 0)isInside = true;
			//if we haven't already found a closer intersection
			if (left_t < _int.dist() || isInside)
				theLeft->intersect(_int);
		}
		if (rightInt /*&& _int doesn't already have a closer intersection*/){
			//determine the closest intersection point in front of the ray
			float right_t = (right_t0 < 0) ? right_t1 : right_t0;
			bool isInside = false;
			if (right_t0 < 0)isInside = true;
			//if we haven't already found a closer intersection
			if (right_t < _int.dist() || isInside)
				theRight->intersect(_int);
		}
	}
	//if this is a leaf node
	else{
		//if the ray intersects the bounding box
		float t0 = -1e30f, t1 = 1e30f;
		bool doesInt = theBBox.intersect(_int.ray(), t0, t1);
		if (doesInt){
			float this_t = (t0 < 0) ? t1 : t0;
			//if _int doesn't already have a closer intersection
			if (this_t < _int.dist()){
				//perform the intersection test on the geometry in this node
				list<Shape*>::iterator it;
				for (it = theShapes.begin(); it != theShapes.end(); it++){
					Shape* _sh = *it;
					_sh->intersect(_int);
				}
			}
		}
		
	}
}
